﻿//Note:default namespace for framework.
var RexToy = {};

RexToy.Object = function () { };

RexToy.extend = function (proto) {
    /// <summary>Declare class body</summary>
    /// <param name = "proto">literal object, contains all method/field in the type</param>
    var baseType = this.__baseType;
    var interfaces = this.__interfaces;
    if (!baseType)
        baseType = RexToy.Object;

    var fnTest = /\bthis.[$]super\b/i;

    for (var bp in baseType.prototype) {
        this.prototype[bp] = baseType.prototype[bp];
    }

    var base = baseType.prototype;

    for (var propName in proto) {
        if (propName == '$constructor' || propName == '__init__' || propName == 'properties') {
            continue;
        }
        var d = proto[propName];
        if ((typeof d) == 'function') {
            if (fnTest.test(d)) {
                var m = (function (fn, name) {
                    return function () {
                        var tmp = this.$super;
                        this.$super = base[name];
                        var ret = fn.apply(this, arguments);
                        this.$super = tmp;
                        return ret;
                    }
                })(d, propName);

                this.prototype[propName] = m;
            } else {
                this.prototype[propName] = d;
            }
        } else {
            this.prototype[propName] = d;
        }
    }

    var propertyBlock = proto['properties'];
    if (propertyBlock && typeof propertyBlock === 'object') {
        for (var name in propertyBlock) {
            RexToy.generateProperty(name, this.prototype, propertyBlock);
        }
    }

    var cur_init = function () {
        for (var p in propertyBlock) {
            var name = '__' + p;
            var val = propertyBlock[p];
            if (val instanceof Array) {
                val = val.clone();
            }
            this[name] = val;
        }
    }

    var bs_init = baseType.prototype.__init__;
    var bs_ctor = baseType.prototype.$constructor;
    var cur_ctor = proto.$constructor;
    var f_init = function () {
        if (bs_init)
            bs_init.apply(this);
        cur_init.apply(this);
    };
    var f_ctor = function () {
        if (bs_ctor)
            bs_ctor.apply(this, arguments);
        if (cur_ctor)
            cur_ctor.apply(this, arguments);
    };
    this.prototype.__init__ = f_init;
    this.prototype.$constructor = f_ctor;

    if (this.__interfaces) {
        for (var i = 0; i < this.__interfaces.length; i++) {
            var methods = this.__interfaces[i].prototype['methods'];

            for (var j = 0; j < methods.length; j++) {
                if (this.prototype[methods[j]] == null) {
                    throw new RexToy.InvalidOperationException('Not implement interface method ' + methods[j]);
                }
            }
        }
    }
};

RexToy.generateProperty = function (name, proto, block) {
    var fieldName = '__' + name;
    var fnGetSetName = name.slice(0, 1).toUpperCase() + name.slice(1);

    var val = block[name];

    var getterName = 'get' + fnGetSetName;
    if (!proto[getterName]) {
        proto[getterName] = function () {
            return this[fieldName];
        };
    }

    var setterName = 'set' + fnGetSetName;
    if (!proto[setterName]) {
        proto[setterName] = function (val) {
            this[fieldName] = val;
        };
    }
};

RexToy.Type = {
    Delimiter: '.',
    getNamespace: function (fqName) {
        var idx = fqName.lastIndexOf(RexToy.Type.Delimiter);
        return fqName.substr(0, idx);
    },

    getShortName: function (fqName) {
        var idx = fqName.lastIndexOf(RexToy.Type.Delimiter);
        return fqName.substr(idx + 1);
    }
};

RexToy.Activator = {
    loadObject: function (objectName) {
        return eval(objectName);
    },

    createInstance: function (className, params) {
        var paramList = "";
        if (arguments.length == 2)
            paramList = params.join(',');
        var expr = "new " + className + "(" + paramList + ")";
        return eval(expr);
    }
};

RexToy.registerNamespace = function (namespacePath) {
    if (!namespacePath) return;
    var rootObject = window;
    var namespaceParts = namespacePath.split('.');
    var iCount = namespaceParts.length;
    for (var i = 0; i < iCount; i++) {
        var currentPart = namespaceParts[i];
        if (!rootObject[currentPart]) {
            rootObject[currentPart] = {};
        }
        rootObject = rootObject[currentPart];
    }
};

RexToy.registerType = function (fqName, baseType, interfaces) {
    /// <summary>Declare a new class</summary>
    /// <param name = "fqName">Full qualified name of type</param>
    /// <param name = "baseType">The base type, if null, will be RexToy.Object</param>
    /// <param name = "baseType">Implemented interfaces, an array, can be null or []</param>
    var ns = RexToy.Type.getNamespace(fqName);
    var shortName = RexToy.Type.getShortName(fqName);
    RexToy.registerNamespace(ns);
    var container = (ns == String.Empty) ? window : RexToy.Activator.loadObject(ns);

    var c = function () {
        var _type = fqName;
        this.getType = function () {
            return _type;
        };

        this.__init__.apply(this);
        this.$constructor.apply(this, arguments);
    };

    c.__baseType = baseType;
    c.__interfaces = interfaces;

    c.extend = RexToy.extend;
    container[shortName] = c;
};

RexToy.AbstractFunction = function () { };

RexToy.registerType('RexToy.Interface');
RexToy.Interface.extend({
    $constructor: function () {
        throw new RexToy.InvalidOperationException('Can not initialize an interface.');
    }
});

RexToy.declare = function (def) {
    var methods = [];
    for (var propName in def) {
        if (def.hasOwnProperty(propName)) {
            var p = def[propName];
            if (typeof p == 'function') {
                methods.push(propName);
            } else {
                //TODO:Enhance:property,throw new RexToy.InvalidOperationException('Only function is allowed to decalre in interface');
            }
        }
    }
    this.prototype['methods'] = methods;
};

RexToy.registerInterface = function (fqName) {
    var ns = RexToy.Type.getNamespace(fqName);
    var shortName = RexToy.Type.getShortName(fqName);
    RexToy.registerNamespace(ns);
    var container = (ns == String.Empty) ? window : RexToy.Activator.loadObject(ns);

    var c = function () {
        var _type = fqName;
        this.getType = function () {
            return _type;
        };
    };

    c.declare = RexToy.declare;
    container[shortName] = c;
};

if (!Function.prototype.bind) {
    Function.prototype.bind = function (obj, args) {
        var __method = this;
        return function () {
            return __method.call(obj, args)
        };
    }
};

RexToy.merge = function(dest, src) {
    for (var property in src) {
        if(src.hasOwnProperty(property)) {
            if(!dest.property) {
                var d = src[property];
                dest.prototype[property] = d;
            }
        }
    }
};

//Note:use merge to enhance js basic class.
String.Empty = '';
String.Space = ' ';

RexToy.merge(String, {
    trimStart: function () {
        return this.replace(/^\s*/, String.Empty);
    },

    trimEnd: function () {
        return this.replace(/\s*$/, String.Empty);
    },

    trim: function () {
        return this.trimStart().trimEnd();
    },

    startWith: function (str) {
        return (this.substr(0, str.length) == str);
    },

    endWith: function (str) {
        return (this.substr(this.length - str.length) == str);
    }
});

String.isNullOrEmpty = function(str) {
    return (str == undefined || str == null || str === String.Empty);
};

String.format = function () {
    var format = arguments[0];
    for (var i = 1; i < arguments.length; i++) {
        var re = new RegExp("\\{" + (i - 1) + "\\}", 'g');
        format = format.replace(re, arguments[i]);
    }
    return format;
};

RexToy.merge(Array, {    
    insertAt: function (item , idx) {
        this.splice(idx, 0, item);
    },
    
    removeAt: function (idx) {
        var item = this[idx];
        if (item) {
            this.splice(idx, 1);
        }
        return item;
    },

    indexOf: function (v) {
        for (var i = this.length; i-- && this[i] !== v; );
        return i;
    },

    contains: function (item) {
        var index = this.indexOf(item);
        return (index >= 0);
    },

    first: function () {
        if (this.length == 0)
            return null;
        else
            return this[0];
    },

    last: function () {
        if (this.length == 0)
            return null;
        else
            return this[this.length - 1];
    },

    forEach: function (lamda) {
        var length = this.length;
        for (var i = 0; i < length; i++) {
            lamda(this[i], i);
        }
    },

    remove: function (item) {
        var index = this.indexOf(item);
        if (index >= 0) {
            this.splice(index, 1);
        }
        return (index >= 0);
    },

    clear: function () {
        if (this.length > 0) {
            this.splice(0, this.length);
        }
    },

    clone: function () {
        return this.slice(0);
    }
});

RexToy.merge(Number, {
    toPadString: function (length, padChar, radix) {
        if (!padChar)
            padChar = '0';
        if (!radix)
            radix = 10;
        var s = this.toString(radix);
        var pad = String.Empty;
        for (var i = 0; i < length - s.length; i++)
            pad += padChar;
        return pad + s;
    }
});

RexToy.Exception = Error;
RexToy.ReadOnlyPropertyException = Error;
RexToy.ArgumentException = Error;
RexToy.ArgumentOutOfRangeException = Error;
RexToy.InvalidOperationException = Error;

RexToy.registerInterface('RexToy.IDisposable');
RexToy.IDisposable.declare({
    dispose: RexToy.AbstractFunction
});

RexToy.registerNamespace("RexToy.Text");
RexToy.registerType("RexToy.Text.StringBuilder");
RexToy.Text.StringBuilder.extend({
    $constructor: function () {
        this._buffer = [];
    },

    append: function (str) {
        this._buffer.push(str);
    },

    appendFormat: function () {
        var format = arguments[0];
        for (var i = 1; i < arguments.length; i++) {
            var re = new RegExp("\\{" + (i - 1) + "\\}", 'g');
            format = format.replace(re, arguments[i]);
        }
        this._buffer.push(format);
    },

    clear: function () {
        this._buffer.clear();
    },

    toString: function () {
        return this._buffer.join(String.Empty);
    }
});

RexToy.registerNamespace("RexToy.Logging");
RexToy.Logging.LogLevel = {
    verbose: 0,
    debug: 10,
    info: 20,
    warn: 30,
    error: 40
};

RexToy.Logging.Logger = {
    _logLevel: 10,
    setLogLevel: function (val) {
        this._logLevel = val;
    },

    verbose: function (msg) {
        if (this._logLevel <= RexToy.Logging.LogLevel.verbose)
            console.log(msg);
    },

    debug: function (msg) {
        if (this._logLevel <= RexToy.Logging.LogLevel.debug)
            console.log(msg);
    },

    info: function (msg) {
        if (this._logLevel <= RexToy.Logging.LogLevel.info)
            console.log(msg);
    },

    warn: function (msg) {
        if (this._logLevel <= RexToy.Logging.LogLevel.warn)
            console.log(msg);
    },

    error: function (msg) {
        if (this._logLevel <= RexToy.Logging.LogLevel.error)
            console.log(msg);
    }
};

RexToy.registerNamespace("RexToy.Ajax");
RexToy.Ajax.Method = {
    GET: 'GET',
    POST: 'POST'
};

RexToy.Ajax.DataFormat = {
    JSON: 'json',
    XML: 'xml',
    HTML: 'html'
};

RexToy.registerType("RexToy.Ajax.Request");
RexToy.Ajax.Request.extend({
    properties: {
        url: String.Empty,
        method: RexToy.Ajax.Method.GET,
        format: RexToy.Ajax.DataFormat.JSON,
        data: null,
        username: String.Empty,
        password: String.Empty,
        onSuccess: null,
        onFailure: null,
        headers: {}
    },

    _xhr: null,
    $constructor: function (param) {
        this._xhr = this.createXHR();
        if (!this._xhr)
            throw new RexToy.InvalidOperationException('XHR not support');

        this.init(param);
    },

    init: function (param) {
        if (!param)
            return;

        this.__url = param.url || this.__url;
        this.__method = param.method || this.__method;
        this.__format = param.format || this.__format;
        this.__data = param.data || this.__data;
        this.__username = param.username || this.__username;
        this.__password = param.password || this.__password;
        this.__onSuccess = param.onSuccess || this.__onSuccess;
        this.__onFailure = param.onFailure || this.__onFailure;
        if (param.headers)
            this.mergeHeaders(param.headers);
    },

    mergeHeaders: function (headers) {
        for (var p in headers) {
            if (headers.hasOwnProperty(p)) {
                this.__headers[p] = headers[p];
            }
        }
    },

    setFormat: function (format) {
        var headers = {
            json: 'application/json',
            xml: 'application/xml',
            html: 'text/html'
        };
        this.__format = format;
        this._xhr.setRequestHeader('Accept', headers[format]);
    },

    createXHR: function () {
        try {
            return new XMLHttpRequest();
        } catch (e) { }
        try {
            return new ActiveXObject('Msxml2.XMLHTTP');
        } catch (e) { }
        try {
            return new ActiveXObject('Microsoft.XMLHTTP');
        } catch (e) { }
        return null;
    },

    call: function (param) {
        this.init(param);

        this.makeHandler(this._xhr, this._callback);

        if (String.isNullOrEmpty(this.__username)) {
            this._xhr.open(this.__method, this.__url, true);
        } else {
            this._xhr.open(this.__method, this.__url, true, this.__username, this.__password);
        }

        this.setFormat(this.__format);
        for (var key in this.__headers) {
            this._xhr.setRequestHeader(key, this.__headers[key]);
        }

        if (param)
            this._xhr.send(param.data || this.__data);
        else
            this._xhr.send(this.__data);
    },

    _callback: function (data, xhr) {
        if (xhr.readyState == 4 && xhr.status == 200) {
            if (this.__onSuccess)
                this.__onSuccess(data, xhr);
        } else {
            if (this.__onFailure)
                this.__onFailure(data, xhr);
        }
    },

    makeHandler: function (xhr, fn) {
        var format = this.__format;
        var self = this;
        xhr.onreadystatechange = function () {//Note: callback(result, xhr)
            if (xhr.readyState == 4) {
                switch (format) {
                    case RexToy.Ajax.DataFormat.HTML:
                        fn && fn.apply(self, [xhr.responseText, xhr]);
                        break;

                    case RexToy.Ajax.DataFormat.XML:
                        fn && fn.apply(self, [xhr.responseXML, xhr]);
                        break;

                    case RexToy.Ajax.DataFormat.JSON:
                        var json = null;
                        try {
                            json = window.JSON.parse(xhr.responseText);
                        } catch (e) {
                            RexToy.Logging.Logger.error('Request JSON format, but fail to parse.');
                        }
                        fn && fn.apply(self, [json, xhr]);
                        break;
                }
            }
        }
    }
});

RexToy.Cookie =
{
    setCookie: function (name, value, expires, path, domain, secure) {
        var today = new Date();
        if (expires) {
            expires = expires * 1000 * 60 * 60 * 24;
        }
        var expires_date = new Date(today.getTime() + expires);
        document.cookie = name + "=" + escape(value) +
		    (expires ? ";expires=" + expires_date.toGMTString() : "") +
		    (path ? ";path=" + path : "") +
		    (domain ? ";domain=" + domain : "") +
		    (secure ? ";secure" : "");
    },

    getCookie: function (name) {
        var start = document.cookie.indexOf(name + "=");
        var len = start + name.length + 1;
        if ((!start) && (name != document.cookie.substring(0, name.length))) {
            return null;
        }
        if (start == -1) return null;
        var end = document.cookie.indexOf(";", len);
        if (end == -1) end = document.cookie.length;

        return unescape(document.cookie.substring(len, end));
    },

    removeCookie: function (name, path, domain) {
        if (this.getCookie(name))
            document.cookie = name + "=" +
			(path ? ";path=" + path : "") +
			(domain ? ";domain=" + domain : "") +
			";expires=Thu, 01-Jan-1970 00:00:01 GMT";
    }
};

RexToy.registerNamespace('RexToy.L10N');
RexToy.L10N.FormatException = Error;

RexToy.registerType('RexToy.L10N.DateFormatter');
RexToy.L10N.DateFormatter = {
    currentFormat: 'mm/dd/yyyy', //Note: default format.

    getCurrentFormat: function () {
        return RexToy.L10N.DateFormatter.currentFormat;
    },

    setCurrentFormat: function (format) {
        RexToy.L10N.DateFormatter.currentFormat = format;
    },

    format: function (date) {
        var r = RexToy.L10N.DateFormatter.currentFormat;
        r = r.replace(/yyyy/g, date.year);
        r = r.replace(/mm/g, date.month.toPadString(2, 0));
        r = r.replace(/dd/g, date.date.toPadString(2, 0));
        return r;
    },

    parse: function (str) {
        var yIdx = RexToy.L10N.DateFormatter.currentFormat.indexOf('yyyy');
        var mIdx = RexToy.L10N.DateFormatter.currentFormat.indexOf('mm');
        var dIdx = RexToy.L10N.DateFormatter.currentFormat.indexOf('dd');
        var y = parseInt(str.substr(yIdx, 4), 10);
        var m = parseInt(str.substr(mIdx, 2), 10);
        var d = parseInt(str.substr(dIdx, 2), 10);
        var r = { year: y, month: m, date: d };
        var str1 = RexToy.L10N.DateFormatter.format(r);

        if (str1 != str)
            throw new RexToy.L10N.FormatException(String.format('Date {0} format {1} incorrect:', str, RexToy.L10N.DateFormatter.currentFormat));
        return r;
    }
};